package com.bsj.media.media;

import com.bsj.consumerQ.until.ExceptionUntil;
import com.bsj.media.media.resample.resampler;
import com.bsj.power.common.def.protocol.rtp.RtpParameterSet;
import com.bsj.power.common.util.Conv;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FlvMediaStream extends mediaStream {

    public boolean isPacketFlvHeader; // 是否打包FLV头
    public int preTagSize; // 上一个tag大小
    public int videotimestamps; // 视频相对时间戳
    public int audiotimestamps; // 音频相对时间戳
    public int st_audioRate = 23;
    public int st_videoRate = 33;
    public int codeType;// 码流类型,0 不做判断，1是主码流，2是子码流，根据码流切换拼的地址来做

    public AudioDecorder2 audioDecoder = null; // 音频解码器
    // public PcmEncoderAAC3 audioEncoder3 = null; // 音频pcm编码成AAC
    public PcmEncoderAAC2 audioEncoder2 = null; // 音频pcm编码成AAC
    //全局变量
    public long startTimestamps;
    //第一次时间戳
    public long firstTimestamps;

    // pcm重采样对象
    private resampler resAudio;
    private byte[] resBuffer; // 重采样的接收buffer，长度用8192
    private int resLen; // buffer 长度

    private byte[] encOneAACNeedPcmSampleBuf = null; // 编码一帧aac，需要pcm的字节数
    private int encOneAACNeedPcmSize = 0; // 编码一帧aac，需要pcm的字节数
    private int encOneAACAlreadySize = 0; // 已经保存的字节大小

    private boolean bDisplay = false; //
    private int oneAudioFrameSize = 0; // 一帧音频大小

    private int audioType = 1; // 音频类型，默认是G711a
    private MediaSaveInfo mediaSaveInfo;

    public MediaSaveInfo getMediaSaveInfo() {
        return mediaSaveInfo;
    }

    public FlvMediaStream(String videoType, int codeType) {
        super.setVideoType(videoType);
        this.audioType = 1;
        this.preTagSize = 0;
        this.videotimestamps = 0;
        this.audiotimestamps = 0;
        this.audioDecoder = new AudioDecorder2();
        this.audioEncoder2 = new PcmEncoderAAC2();
        this.isPacketFlvHeader = false;

        this.resAudio = new resampler();
        this.resAudio.speex_resampler_init(1, 8000, 44100, 0, 0);
        this.resAudio.speex_resampler_skip_zeros();

        this.resLen = 8192;
        this.resBuffer = new byte[resLen];

        this.codeType = codeType;
    }

    public void liveVideo() {
        startTimestamps = System.currentTimeMillis();
    }

    @Override
    public void setClosed(boolean isClosed) {
        super.setClosed(isClosed);
        if (isClosed) {
            if (audioEncoder2 != null) {
                audioEncoder2.release();
                audioEncoder2 = null;
            }
            if (encOneAACNeedPcmSampleBuf != null) {
                encOneAACNeedPcmSampleBuf = null;
            }
        }
    }

    /**
     * 打包FLV头信息
     *
     * @param body 原始数据
     * @param dst
     * @return
     */
    public int PacketFirstFLV(byte[] body, byte[] dst) {
        int mediaLen = 0; // 打包每帧数据长度
        int dstOffset = 0;

        int freamRate = ParserVideoData.getVideoFrame(body, 0);
        // audioType==3,表示当前音频是aac，采样率是16000
        mediaLen = ParserVideoData.packetFlvHeader(dst, dstOffset, 0, freamRate == 0 ? 15 : freamRate, getVideoType(),
                width, height, 0, 0, audioType == 3 ? 16000 : 41000, ParserVideoData.getAudioType(body));
        // 前一个tag大小，减4是preTageSize存储字节数，减9是FLV头字节数
        preTagSize = mediaLen - 4 - 9;
        dstOffset += mediaLen;

        if (getVideoType() == 1) {
            // 这里打包关键帧里面的SPS，PPS数据
            mediaSaveInfo = new MediaSaveInfo();
            mediaLen = ParserVideoData.packetFlvData(body, dst, dstOffset, false, preTagSize, mediaSaveInfo);
            if (mediaLen <= 4) {
                log.warn("videoType: " + getVideoType() + " 打包头---sps,pps数据有错，丢掉---key:");
                return 0;
            }
            preTagSize = mediaLen - 4;
            // 改变buffer中时间戳，preTagSize:4, tag type:1, bodysize:3
            Conv.setIntNetOrder_3(dst, dstOffset + 8, videotimestamps);
            videotimestamps += st_videoRate;
            dstOffset += mediaLen;

            // 音频，打包第一个音频tag帧
            mediaLen = ParserVideoData.packetFirstAudioTag(dst, dstOffset, preTagSize, audioType);
            if (mediaLen <= 5) {
                log.warn("打包第一个音频tag帧出错！！ --key:");
                return 0;
            }

            preTagSize = mediaLen - 4;
            // 改变buffer中时间戳，preTagSize:4, tag type:1, bodysize:3
            Conv.setIntNetOrder_3(dst, dstOffset + 8, audiotimestamps);
            audiotimestamps += st_audioRate;
            dstOffset += mediaLen;
        }
        isPacketFlvHeader = true;

        // 上面打包了h264 sps pps信息，在这边加入0x65 I 帧数据
        int srcOffset = 21;
        // 根据h264或者h265来选择打包的数据
        if (body[2] == RtpParameterSet.fdVideoCodeTypeH265) {
            mediaLen = ParserVideoData.packetH265IVideoData(body, srcOffset, dst, dstOffset, 0, freamRate, preTagSize);
        } else {
            mediaLen = ParserVideoData.packetIVideoData(body, srcOffset, dst, dstOffset, 0, freamRate, preTagSize);
        }

        preTagSize = mediaLen - 4;
        videotimestamps += st_videoRate;
        dstOffset += mediaLen;
        return dstOffset;
    }

    /**
     * 打包FLV头后，打包其他FLV信息
     *
     * @param body
     * @param dst
     * @return
     */
    public int PacketSecondFLV(byte[] body, byte[] dst, int dstOffset) {
        // 发送数据
        int mediaLen = 0;
        int srcOffset = 1; // chn
        // 数据类型，1字节。0：视频I帧 1：视频P帧 2：视频B帧 3：音频帧 4：透传数据
        int dataType = Byte.toUnsignedInt(body[srcOffset++]);
        // 编码格式 1-h264  3-h265
        int encodingFormat = Byte.toUnsignedInt(body[2]);
        // h264数据偏移
        long nowTime = Conv.getLongNetOrder(body, 5);
        srcOffset = 21;
        switch (dataType) {
            case 0: // 视频I帧
                if (!isPacketFlvHeader) {
                    if (encodingFormat == RtpParameterSet.fdVideoCodeTypeH264) {
                        // 打包h264 关键帧中的SPS，PPS信息
                        mediaLen = ParserVideoData.packetH264Header(body, srcOffset, dst, dstOffset, preTagSize);
                    } else if (encodingFormat == RtpParameterSet.fdVideoCodeTypeH265) {
                        mediaLen = ParserVideoData.packetH265Header(body, srcOffset, dst, dstOffset, preTagSize);
                    }
                } else {
                    // 打包h264 关键帧中的SPS，PPS信息
                    // mediaLen = ParserVideoData.packetH264Header(body, srcOffset, dst, dstOffset,
                    // preTagSize);
                    // preTagSize = mediaLen - 4;
                    // dstOffset += mediaLen;
                    int timeNow = videotimestamps;
                    if (startTimestamps > 0) {
                        timeNow = (int) (nowTime - firstTimestamps);
                    }
                    // 打包视频I帧
                    if (encodingFormat == RtpParameterSet.fdVideoCodeTypeH265) {
                        mediaLen = ParserVideoData.packetH265IVideoData(body, srcOffset, dst, dstOffset, timeNow, 15,
                                preTagSize);
                    } else {
                        mediaLen = ParserVideoData.packetIVideoData(body, srcOffset, dst, dstOffset, timeNow, 15,
                                preTagSize);
                    }

                    preTagSize = mediaLen - 4;
                    videotimestamps += st_videoRate;
                }
                break;
            case 1:
            case 2:// 视频P，B帧Ha
                if (!isPacketFlvHeader) {
                    mediaLen = -1;
                } else {
                    int timeNow = videotimestamps;
                    if (startTimestamps > 0) {
                        timeNow = (int) (nowTime - firstTimestamps);

                    }
                    // 打包视频B，P帧
                    mediaLen = ParserVideoData.packetBPVideoData(body, srcOffset, dst, dstOffset, timeNow, 15,
                            preTagSize);
                    preTagSize = mediaLen - 4;
                    videotimestamps += st_videoRate;
                }
                break;
            case 3: // 音频帧
            {
                if (audioType != 3) {
                    if (!audioDecoder.isInit()) {
                        // 还没发送音频头，先发送,设备为单通道，采用率为8000
                        if (!audioDecoder.init(audioType, 1, 8000)) {
                            log.error("初始化解码器失败！");
                        }
                    }
                    if (!audioEncoder2.isInit()) {
                        if (!audioEncoder2.init(1, 44100, 64000, 1, 8000)) {
                            log.error("初始化编码器失败！");
                        } else {
                            setEncOneAACNeedPcmSize(audioEncoder2.getNeedSampleNum());
                        }
                    }
                    if (!isPacketFlvHeader) {
                        // 打包第一个音频帧

                    } else {
                        setOneAudioFrameSize(audioType);
                        // 音频返回解析后的audio数据
                        System.arraycopy(body, srcOffset, dst, dstOffset, body.length - srcOffset);
                        mediaLen = body.length - srcOffset;
                    }
                } else {
                    log.warn("进入aac打包里面");
                    // aac 编码
                    if (isPacketFlvHeader) {
                        int len = body.length - srcOffset;
                        int adtsHeaderLen = ParserVideoData.getAdtsAACHeaderLen(body, srcOffset, len);
                        int aacLen = ParserVideoData.getAdtsAACRawLen(body, srcOffset, len);
                        if (adtsHeaderLen > 0 && aacLen > 0) {
                            // 音频返回解析后的audio数据
                            System.arraycopy(body, adtsHeaderLen, dst, dstOffset, aacLen - adtsHeaderLen);
                            mediaLen = aacLen - adtsHeaderLen;
                        }
                    }
                }
            }
            case 4: // 透传数据
                break;
            default:
                break;
        }

        if (mediaLen == -5) {
            log.warn("打包I帧数据，找不到 I帧0x65 : " + mediaLen);
            // writeFileSystem(body, 0, body.length, getUri());
        } else if (mediaLen == -7) {
            log.warn("打包I帧数据，找不到 SPS : " + mediaLen);
            // writeFileSystem(body, 0, body.length, getUri());
        } else if (mediaLen == -8) {
            log.warn("打包I帧数据，找不到 PPS : " + mediaLen);
            // writeFileSystem(body, 0, body.length, getUri());
        } else if (mediaLen == -9) {
            log.warn("打包P帧数据，找不到h264头  : " + mediaLen);
            // writeFileSystem(body, 0, body.length, getUri());
        } else if (mediaLen <= 0) {

        } else {

            if (ParserVideoData.getTypeFrame(body) == 1) {
                // 视频帧直接打包成功
                dstOffset += mediaLen;
            } else {
                // 非视频帧
                if (getVideoType() == 1 || getVideoType() == 3) {
                    // 只有当客户端请求类型是音视频，或音频时，才发送音频
                    // 音频转码后在打包发送
                    dstOffset = packetAAC(dst, dstOffset, mediaLen, dst, dstOffset, nowTime);
                }
            }
        }

        return dstOffset;
    }

    /**
     * 编码AAC，并打包音频包
     *
     * @param dst
     * @return
     */
    public int packetAAC(byte[] audioData, int srcOffset, int mediaLen, byte[] dst, int dstOffset, long nowTime) {

        if (audioType != 3) {
            if (audioDecoder.isInit() && audioEncoder2.isInit()) {
                byte[] pcm = new byte[4096];
                // 4字节海思头
                int lenSize = ((audioData[srcOffset + 2] & 0xFF) + (audioData[srcOffset + 3] << 8 & 0xFF)) * 2;
                int audioSize = mediaLen - 4;
                int ret = 0;

                int realOffset = 0;
                if (lenSize == audioSize) {
                    // 有海思头
                    realOffset = 4;
                } else {
                    // 没有海思头
                    realOffset = 0;
                    audioSize = mediaLen;
                    // 只打印 一次
                    if (!bDisplay) {
                        log.warn("收到音频数据大小不对，hisi头显示大小：" + lenSize + " 实际大小：" + audioSize + " 数据无海思头，长度为："
                                + mediaLen);
                        bDisplay = true;
                    }
                }

                // long dwTime1 = System.nanoTime();
                ret = audioDecoder.DecoderSamples(audioData, srcOffset + realOffset, mediaLen - realOffset, pcm);
                // long dwTime2 = System.nanoTime();
                if (ret > 0) {
                    // 重采样
                    int resapmleLen = resAudio.speex_resampler_process_int(0, pcm, ret / 2, resBuffer, resLen);
                    // long dwTime3 = System.nanoTime();
                    int pcmLen = resapmleLen * 2;
                    // byte[] aac = new byte[2048];
                    // aac 编码使用dst 300*1024后面的内存
                    int aacOffset = 300 * 1024;
                    int pcmOffset = 0;
                    // int pcmLen = ret;

                    while (pcmLen > 0) {
                        if (encOneAACAlreadySize + pcmLen >= encOneAACNeedPcmSize) {
                            int needSampleCopy = encOneAACNeedPcmSize - encOneAACAlreadySize;
                            try {
                                System.arraycopy(resBuffer, pcmOffset, encOneAACNeedPcmSampleBuf, encOneAACAlreadySize,
                                        needSampleCopy);
                            } catch (Exception e) {
                                log.error("拷贝出现异常！ needSampleCopy:" + needSampleCopy + " pcmOffset:" + pcmOffset + " encOneAACAlreadySize:" + encOneAACAlreadySize +
                                        ExceptionUntil.getErrorStr(e));
                            }
                            // System.arraycopy(pcm, pcmOffset, encOneAACNeedPcmSampleBuf,
                            // encOneAACAlreadySize,
                            // needSampleCopy);
                            // 拷贝满一次编码aac PCM缓冲区后，已经有的size置零
                            encOneAACAlreadySize = 0;
                            pcmOffset += needSampleCopy;
                            pcmLen -= needSampleCopy;

                            int encRet = audioEncoder2.Encodec(encOneAACNeedPcmSampleBuf, 0, encOneAACNeedPcmSize, dst,
                                    aacOffset, 307200);
                            // int srcSample = encOneAACNeedPcmSize / 2;
                            // int encRet = audioEncoder3.Encodec(encOneAACNeedPcmSampleBuf, 0,
                            // encOneAACNeedPcmSize,
                            // srcSample, aac, 0, 1024);
                            if (encRet > 0) {
                                int timeNow = videotimestamps;
                                if (startTimestamps > 0) {
                                    timeNow = (int) (nowTime - firstTimestamps);
//                                    log.info("音频时间为:" + nowTime );
                                }
                                mediaLen = ParserVideoData.packetAudioVideoData(dst, aacOffset, encRet, dst, dstOffset,
                                        timeNow, false, preTagSize, 0, 1);
                                preTagSize = mediaLen - 4;
                                audiotimestamps += st_audioRate;

                                dstOffset += mediaLen;
                            }

                            if (dstOffset > aacOffset) {
                                log.warn("打包后FLV数据大小：" + dstOffset);
                                break;
                            }
                        } else {
                            System.arraycopy(resBuffer, pcmOffset, encOneAACNeedPcmSampleBuf, encOneAACAlreadySize,
                                    pcmLen);

                            encOneAACAlreadySize += pcmLen;
                            pcmLen = 0;
                        }
                    }
                }
            }
        } else {
            int timeNow = videotimestamps;
            if (startTimestamps > 0) {
                timeNow = (int) (nowTime - firstTimestamps);
            }
            // aac 直接打包
            mediaLen = ParserVideoData.packetAudioVideoData(audioData, srcOffset, mediaLen, dst, dstOffset, timeNow, false, preTagSize, 16000, 1);
            preTagSize = mediaLen - 4;
            audiotimestamps += st_audioRate;

            dstOffset += mediaLen;
        }

        return dstOffset;
    }

    /**
     * 打包成flv，再发送
     */
    @Override
    public int PacketData(byte[] FdFdBody, int offset, int len, byte[] dstBuffer) {
        if (FdFdBody != null) {
            int packetLen = 0;
            try {
                // 提取FDFD协议数据的数据体，19字节+body+校验1+结束位1
                byte[] body = FdFdBody;
                if (!isPacketFlvHeader) {
                    if (getVideoType() != 3 && (!ParserVideoData.checkIframe(body))) {
                        // 如果是只有音频的时候不判断这个
                        log.warn("打包头---不是I帧，丢掉---key:");
                        return 0;
                    }
//                     videoRate = 0;
                    if (getVideoType() != 3) {
                        // 算出时间间隔
                        int videoRate = ParserVideoData.getVideoFrame(body, 0);
                        if (videoRate > 0) {
                            st_videoRate = 1000 / videoRate;
                        }
                        // 获取视频分辨率0
                        int solutionRatio = ParserVideoData.getVideoSolution(body, 0);
                        getSolutionRatio(solutionRatio);
                    } else {
                        log.info("进入PC端对讲数据打包：");
                    }
                    // 打FLV头，h264信息
                    packetLen = PacketFirstFLV(body, dstBuffer);
                } else {
                    packetLen = PacketSecondFLV(body, dstBuffer, 0);
                }
            } catch (Exception e) {
                log.error("打包数据异常！" + ExceptionUntil.getErrorStr(e));
            }
            return packetLen;
        }
        return 0;
    }

    public int getEncOneAACNeedPcmSize() {
        return encOneAACNeedPcmSize;
    }

    public void setEncOneAACNeedPcmSize(int encOneAACNeedPcmSize) {
        if (this.encOneAACNeedPcmSize == 0) {
            this.encOneAACNeedPcmSize = encOneAACNeedPcmSize;
            encOneAACNeedPcmSampleBuf = new byte[this.encOneAACNeedPcmSize];
            log.info("aac 需要的pcm大小：" + encOneAACNeedPcmSize);
        }
    }

    public int getOneAudioFrameSize() {
        return oneAudioFrameSize;
    }

    public void setOneAudioFrameSize(int oneAudioFrameSize) {
        if (oneAudioFrameSize == 0) {
            this.oneAudioFrameSize = 164;
        } else if (oneAudioFrameSize == 1) {
            this.oneAudioFrameSize = 320;
        } else if (oneAudioFrameSize == 2) {
            this.oneAudioFrameSize = 160;
        }
    }
}